package com.cocolover2.andbase.http.ws;

import android.os.Handler;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;


public class WsManager implements IWsManager {
    private static final String TAG = "WsManager";
    private int reconnectInterval = 10 * 1000;//重连自增步长
    private int reconnectMaxTime = 120 * 1000;//最大重连间隔
    private String wsUrl;
    private WebSocket mWebSocket;
    private OkHttpClient mOkHttpClient;
    private Request mRequest;
    private int mCurrentStatus = WsStatus.DISCONNECTED;     //websocket连接状态
    private boolean isNeedReconnect;          //是否需要断线自动重连
    private boolean isManualClose = false;         //是否为手动关闭websocket连接
    private WsStatusListener wsStatusListener;
    private Lock mLock;
    private Handler mHandler = new Handler();
    private int reconnectCount = 0;   //重连次数
    private Runnable reconnectRunnable = new Runnable() {
        @Override
        public void run() {
            if (wsStatusListener != null && wsStatusListener.onReconnect(reconnectCount)) {
                buildConnect();
            } else {
                reconnectCount = 0;
            }
        }
    };
    private WebSocketListener mWebSocketListener = new WebSocketListener() {

        @Override
        public void onOpen(WebSocket webSocket, final Response response) {
            mWebSocket = webSocket;
            setCurrentStatus(WsStatus.CONNECTED);
            //连接成功后，取消重连
            cancelReconnect();
            if (wsStatusListener != null) {
                wsStatusListener.onOpen(response);
            }
        }

        @Override
        public void onMessage(WebSocket webSocket, final ByteString bytes) {
            if (wsStatusListener != null) {
                wsStatusListener.onMessage(bytes);
            }
        }

        @Override
        public void onMessage(WebSocket webSocket, final String text) {
            if (wsStatusListener != null) {
                wsStatusListener.onMessage(text);
            }
        }

        @Override
        public void onClosing(WebSocket webSocket, final int code, final String reason) {
            if (wsStatusListener != null) {
                wsStatusListener.onClosing(code, reason);
            }
        }

        @Override
        public void onClosed(WebSocket webSocket, final int code, final String reason) {
            if (wsStatusListener != null) {
                wsStatusListener.onClosed(code, reason);
            }
        }

        @Override
        public void onFailure(WebSocket webSocket, final Throwable t, final Response response) {
            if (wsStatusListener != null) {
                wsStatusListener.onFailure(t, response);
            }
            //异常断开重连
            tryReconnect();
        }
    };

    public WsManager(Builder builder) {
        wsUrl = builder.wsUrl;
        isNeedReconnect = builder.needReconnect;
        mOkHttpClient = builder.mOkHttpClient;
        reconnectInterval = builder.reconnectInterval;
        reconnectMaxTime = builder.reconnectMaxTime;
        this.mLock = new ReentrantLock();
    }

    private void initWebSocket() {
        if (mOkHttpClient == null) {
            mOkHttpClient = new OkHttpClient.Builder()
                    .retryOnConnectionFailure(true)
                    .build();
        }
        if (mRequest == null) {
            mRequest = new Request.Builder()
                    .url(wsUrl)
                    .build();
        }
        mOkHttpClient.dispatcher().cancelAll();
        try {
            mLock.lockInterruptibly();
            try {
                mOkHttpClient.newWebSocket(mRequest, mWebSocketListener);
            } finally {
                mLock.unlock();
            }
        } catch (InterruptedException e) {
        }
    }

    @Override
    public WebSocket getWebSocket() {
        return mWebSocket;
    }


    public void setWsStatusListener(WsStatusListener wsStatusListener) {
        this.wsStatusListener = wsStatusListener;
    }

    @Override
    public synchronized boolean isWsConnected() {
        return mCurrentStatus == WsStatus.CONNECTED;
    }

    @Override
    public synchronized int getCurrentStatus() {
        return mCurrentStatus;
    }

    @Override
    public synchronized void setCurrentStatus(int currentStatus) {
        this.mCurrentStatus = currentStatus;
    }

    @Override
    public void startConnect() {
        isManualClose = false;
        buildConnect();
    }

    @Override
    public void stopConnect() {
        isManualClose = true;
        disconnect();
    }

    private void tryReconnect() {
        if (!isNeedReconnect | isManualClose) {
            return;
        }
        setCurrentStatus(WsStatus.RECONNECT);

        long delay = reconnectCount * reconnectInterval;
        mHandler.postDelayed(reconnectRunnable, delay > reconnectMaxTime ? reconnectMaxTime : delay);
        reconnectCount++;
    }

    private void cancelReconnect() {
        mHandler.removeCallbacks(reconnectRunnable);
        reconnectCount = 0;
    }

    private void disconnect() {
        if (mCurrentStatus == WsStatus.DISCONNECTED) {
            return;
        }
        cancelReconnect();
        if (mOkHttpClient != null) {
            mOkHttpClient.dispatcher().cancelAll();
        }
        if (mWebSocket != null) {
            boolean isClosed = mWebSocket.close(WsStatus.CODE.NORMAL_CLOSE, WsStatus.TIP.NORMAL_CLOSE);
            //非正常关闭连接
            if (!isClosed) {
                if (wsStatusListener != null) {
                    wsStatusListener.onClosed(WsStatus.CODE.ABNORMAL_CLOSE, WsStatus.TIP.ABNORMAL_CLOSE);
                }
            }
        }
        setCurrentStatus(WsStatus.DISCONNECTED);
    }

    private synchronized void buildConnect() {
        switch (getCurrentStatus()) {
            case WsStatus.CONNECTED:
            case WsStatus.CONNECTING:
                break;
            default:
                setCurrentStatus(WsStatus.CONNECTING);
                initWebSocket();
        }
    }

    //发送消息
    @Override
    public boolean sendMessage(String msg) {
        return send(msg);
    }

    @Override
    public boolean sendMessage(ByteString byteString) {
        return send(byteString);
    }

    private boolean send(Object msg) {
        boolean isSend = false;
        if (mWebSocket != null && mCurrentStatus == WsStatus.CONNECTED) {
            if (msg instanceof String) {
                isSend = mWebSocket.send((String) msg);
            } else if (msg instanceof ByteString) {
                isSend = mWebSocket.send((ByteString) msg);
            }
            //发送消息失败，尝试重连
            if (!isSend) {
                tryReconnect();
            }
        }
        return isSend;
    }


    public static final class Builder {

        private String wsUrl;
        private boolean needReconnect = true;
        private OkHttpClient mOkHttpClient;
        private int reconnectInterval = 10 * 1000;//重连自增步长
        private int reconnectMaxTime = 120 * 1000;//最大重连间隔

        public Builder() {
        }

        public Builder wsUrl(String wsUrl) {
            this.wsUrl = wsUrl;
            return this;
        }

        /**
         * 重连递增周期(每次重连周期增加的时间间隔，达到最大周期时，使用最大周期)
         *
         * @param reconnectInterval 单位：ms
         * @return
         */
        public Builder reconnectInterval(int reconnectInterval) {
            if (reconnectInterval > 0) {
                this.reconnectInterval = reconnectInterval;
            }
            return this;
        }

        /**
         * 重连的最大周期
         *
         * @param reconnectMaxTime 单位：ms
         * @return
         */
        public Builder reconnectMaxTime(int reconnectMaxTime) {
            if (reconnectMaxTime > 0) {
                this.reconnectMaxTime = reconnectMaxTime;
            }
            return this;
        }

        public Builder client(OkHttpClient client) {
            mOkHttpClient = client;
            return this;
        }

        public Builder needReconnect(boolean isNeedReconnect) {
            needReconnect = isNeedReconnect;
            return this;
        }

        public WsManager build() {
            return new WsManager(this);
        }

    }
}
